// src/main/java/com/dbms/storage/Page.java
package com.dbms.storage;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock; // 【新增】导入读写锁，用于页面级并发控制

/**
 * Page 类：代表缓冲池（BufferPool）中的一个内存页面，是数据库数据在内存中的载体。
 * 核心职责：存储页面原始字节数据，维护页面元数据（如页面ID、钉住状态、脏页标记），
 * 【已升级】新增页面级读写锁，实现并发场景下的安全访问（读多写少场景优化）。
 */
public class Page {

    // --- 核心数据：页面实际存储的字节数据 ---
    /**
     * 页面数据数组，大小固定为DiskManager.PAGE_SIZE（4KB），
     * 直接对应磁盘上一个页面的原始字节内容，是内存与磁盘数据交互的核心载体。
     */
    private final byte[] data;

    // --- 元数据：管理页面的辅助信息（类似“便签”） ---
    /** 页面唯一标识（Page ID），与磁盘上的页面一一对应；-1 表示页面未关联磁盘页面 */
    private int pageId;
    /**
     * 页面钉住计数（Pin Count）：记录当前有多少线程正在使用该页面，
     * 用AtomicInteger保证多线程下的原子操作（避免竞态条件），
     * pinCount > 0 时页面不可被LRU替换。
     */
    private final AtomicInteger pinCount;
    /** 脏页标记（Dirty Flag）：true 表示页面在内存中被修改过，尚未刷回磁盘；false 表示与磁盘数据一致 */
    private boolean isDirty;

    // --- 【新增】并发控制组件 ---
    /**
     * 页面级读写锁：实现“多读单写”的并发控制策略，
     * 读锁（共享锁）：多个线程可同时获取，适合查询操作；
     * 写锁（排他锁）：仅一个线程可获取，适合修改操作；
     * 解决多线程访问页面时的数据一致性问题，同时提升读操作并发效率。
     */
    private final ReentrantReadWriteLock pageLock;


    /**
     * 构造方法：初始化一个页面，指定初始页面ID
     * @param pageId 页面初始ID（-1 表示未关联磁盘页面的空白页面）
     */
    public Page(int pageId) {
        this.data = new byte[DiskManager.PAGE_SIZE]; // 初始化4KB数据数组
        this.pageId = pageId; // 绑定初始页面ID
        this.pinCount = new AtomicInteger(0); // 初始钉住计数为0（无线程使用）
        this.isDirty = false; // 初始非脏页（未修改）
        this.pageLock = new ReentrantReadWriteLock(); // 初始化读写锁
    }

    /**
     * 【新增】初始化页面为空白的TablePage（表数据页面）
     * 作用：将当前普通Page转换为存储表数据的专用页面，初始化表页面的元数据（如槽位信息）
     */
    public void init() {
        // 借助TablePage类的构造和init方法，初始化表页面结构
        new TablePage(this).init();
    }

    // --- 【新增】读写锁操作方法：对外提供页面级并发控制接口 ---

    /**
     * 获取页面的读锁（共享锁）
     * 适用场景：线程需要读取页面数据（无修改）
     * 特性：多个线程可同时获取读锁；若有线程持有写锁，当前线程会阻塞，直到写锁释放
     */
    public void rLock() {
        this.pageLock.readLock().lock();
    }

    /**
     * 释放页面的读锁
     * 注意：必须与rLock()成对调用，避免锁泄漏（未释放导致其他线程永久阻塞）
     */
    public void rUnlock() {
        this.pageLock.readLock().unlock();
    }

    /**
     * 获取页面的写锁（排他锁）
     * 适用场景：线程需要修改页面数据（如插入、删除记录）
     * 特性：仅一个线程可获取写锁；若有线程持有读锁或写锁，当前线程会阻塞，直到所有锁释放
     */
    public void wLock() {
        this.pageLock.writeLock().lock();
    }

    /**
     * 释放页面的写锁
     * 注意：必须与wLock()成对调用，避免锁泄漏
     */
    public void wUnlock() {
        this.pageLock.writeLock().unlock();
    }


    // --- Getter 和 Setter 方法：访问和修改页面元数据/数据 ---

    /**
     * 获取页面的原始字节数据数组（直接引用，修改会影响页面内容）
     * @return 页面数据数组（大小为4KB）
     */
    public byte[] getData() {
        return data;
    }

    /**
     * 获取当前页面的ID
     * @return 页面ID（-1 表示未关联磁盘页面）
     */
    public int getPageId() {
        return pageId;
    }

    /**
     * 设置页面的ID（通常在页面复用或关联新磁盘页面时调用）
     * @param pageId 新的页面ID
     */
    public void setPageId(int pageId) {
        this.pageId = pageId;
    }

    /**
     * 获取当前页面的钉住计数
     * @return 钉住计数（当前使用该页面的线程数）
     */
    public int getPinCount() {
        return pinCount.get();
    }

    /**
     * 原子增加钉住计数（线程使用页面时调用，标记页面正在被使用）
     */
    public void incrementPinCount() {
        this.pinCount.incrementAndGet();
    }

    /**
     * 原子减少钉住计数（线程使用页面结束时调用，标记页面使用完成）
     * 安全处理：仅当计数>0时才减少，避免负计数
     */
    public void decrementPinCount() {
        if (pinCount.get() > 0) {
            this.pinCount.decrementAndGet();
        }
    }

    /**
     * 判断页面是否为脏页（内存数据与磁盘不一致）
     * @return true 表示脏页，false 表示非脏页
     */
    public boolean isDirty() {
        return isDirty;
    }

    /**
     * 设置页面的脏页标记（修改页面数据后需设为true，刷盘后设为false）
     * @param dirty 新的脏页状态
     */
    public void setDirty(boolean dirty) {
        isDirty = dirty;
    }


    /**
     * 重置页面状态：当旧页面被复用（承载新磁盘页面）时调用，清除旧元数据
     * 作用：避免旧页面的元数据（如pageId、pinCount）影响新页面的使用
     */
    public void reset() {
        this.pageId = -1; // 重置为未关联磁盘页面的ID
        this.pinCount.set(0); // 重置钉住计数为0
        this.isDirty = false; // 重置为非脏页
        // 注意：读写锁不需要重置，锁对象可复用（新线程获取锁时会基于新状态判断）
    }
}